void((function(){

  var UserScript = {
    name:     'ggSpamFree',
    namespace:  'http://code.google.com/p/ggspamfree/',
    version:   VERSION,
    include:   'http://groups.google.tld/group/*/topics*',
    unwrap:    true,
    description: 'Google Groups spam filter',
    copyright:  '(c)2010, Nick',
    license:   {
      type: 'GPL version 3 or any later version',
      url: 'http://www.gnu.org/copyleft/gpl.html',
      URL: 'http://www.gnu.org/copyleft/gpl.html'
    },
    contributor: ['Evertjan','Francesco S. Carta','Jorge','VK']
  }
  
  //#ifdef EXT_USER
  var stopwords = [
      '~~|##|\\!\\!\\!|\\$\\$\\$'
      //#include "stopwords.js"
    ];
  //#else
  var stopwords = [];
  //#endif

  var whitelist = [];

  //#ifdef EXT_CHROME
  chrome.extension.sendRequest({"getStorage": true}, 
    function(ls) { 
      console.log(ls);
      stopwords = ls["master"].trim(' \r\n').split('\n')
        .concat(ls["stopwords"].trim(' \r\n').split('\n'));
      whitelist = ls["whitelist"].trim(' \r\n').split('\n');
      for (var i = stopwords.length; i--;) if (!stopwords[i]) { 
        stopwords.splice(i,1);
        i = stopwords.length;
      }
      for (var i = whitelist.length; i--;) if (!whitelist[i]) { 
        whitelist.splice(i,1);
        i = whitelist.length;
      }
      go();
    });
  //#else
  go();
  //#endif

  /*  Internal service functions:   */

  function ce (tag) {
   return document.createElement(tag);
  }

  function checkNodeMethod (methodName) {
    return typeof document.body[methodName] == 'function';
  }

  /*  Despamming routine   */

  function go () {

    var WORD_START = '(\\W|^)', WORD_END = '(\\W|$)';
    
    var filter = new RegExp(WORD_START 
    + stopwords.join(WORD_END + '|' + WORD_START) 
    + WORD_END, 'i');
    
    var unfilter = new RegExp(WORD_START 
    + whitelist.join(WORD_END + '|' + WORD_START) 
    + WORD_END, 'i');

    /* Style class name of div where the table
     * with messages is contained. This div has
     * no id in the current Google Groups layout.
     */
    var CONTENT_DIV_CLASS = 'maincontoutboxatt';

    var i = 0;
    var found = false;

    /* Finding the content div in div collection:
     */
    var div = document.getElementsByTagName('DIV');
    for (i=0; i<div.length; i++) {
      if (div[i].className != CONTENT_DIV_CLASS) continue;
      div = div[i];
      found = true;
      break;
    }


    /* Google Groups layout may change at any time.
     * Rather than just silently abort we should also
     * inform our user about it using Error Console:
     */
    if (!found) {
      throw new Error(
        UserScript.name.concat(' ', UserScript.version, ': ', 
          'Div [@class="', CONTENT_DIV_CLASS, '"] not found.')
      );
      return;
    }

    /* Get table elements inside the found div;
     * Be ready for a substantial change of
     * Google Groups layout:
     */
    var tables = div.getElementsByTagName('TABLE');
    if (tables.length == 0) {
      throw new Error(
        UserScript.name.concat(' ', UserScript.version, ': ', 
          'No tables found in the content div')
      );
      return;
    }

    /* Topic List or Topic Summary view?
     * In Topic List view it is a single table with
     * each posting in a separate six columns row.
     * In Topic Summary view each posting has its
     * separate two columns table. That requires
     * rather different coding approaches.
     */
    var summary = (tables.length > 1);

    /* Getting the posting collection:
     */
    var posting = summary ? tables : tables[0].tBodies[0].rows;

    /* Getting the parent element for the postings:
     */
    var parent = summary ? div : tables[0].tBodies[0];
     
    var spamCount=0;
    var spamContainer = document.createElement('table');
    spamContainer.style.display='none';
    
    /* Get the table head for summary list view: 
    */
    if (!summary) 
      spamContainer.appendChild(posting[0].cloneNode(true));
      
    /* Filtering spam: 
    */
    console.dir(filter);
    for (i = summary ? 0 : 2; i<posting.length; i++) {
      var p = summary? posting[i].rows[0] : posting[i];
      var text = p.cells[1].textContent || p.cells[1].innerText;
      /* is it spam? */
      if ( ( text.toUpperCase() == text || filter.test(text))
          && (whitelist.length==0 || !unfilter.test(text))) {
        var s = posting[i].cloneNode(true);
        s.style.backgroundColor=['white','#f8f8f8'][spamCount++%2];
        spamContainer.appendChild(s);
        posting[i].style.display='none';
      }
    }
    var c = 0;
    for (i = summary ? 0 : 2; i<posting.length; i++) {
      if (posting[i].style.display!='none')
        posting[i].style.backgroundColor=['white','#f8f8f8'][c++%2];
    }
    
    if (spamCount) {
      var spamOuter = ce('div');
      var toggle = ce('input');
      toggle.type='button';
      toggle.value='Show spam';
      toggle.style.margin='2em';
      toggle.onclick= function(){
        var s=this.nextSibling.style; 
        if (s.display=='none') { 
          s.display = 'block'; this.value = 'Hide spam'; 
        } else { 
          s.display = 'none'; this.value = 'Show spam'; 
        }
      }
      spamOuter.appendChild(toggle);
      spamOuter.appendChild(spamContainer);
      tables[0].parentNode.appendChild(spamOuter);
    }
    
    //#ifdef EXT_CHROME
    chrome.extension.sendRequest({"spamCount": spamCount}, 
      function(response) { console.log(response.message) });
    //#endif
  }

})())
